﻿// Roster form, (c)2010 Knihovnik
// This file is licensed under LGPL license, see more at http://www.gnu.org/licenses/lgpl.html
namespace FacebookChat
{

	#region Usings

	using System;
	using System.Collections.Generic;
	using System.Drawing;
	using System.Runtime.InteropServices;
	using System.Windows.Forms;
	using FacebookChat.Properties;
	using jabber;
	using jabber.client;
	using jabber.protocol.client;
	using jabber.protocol.iq;

	#endregion

	/// <summary>
	/// Roster form
	/// </summary>
	public partial class RosterForm : Form
	{

		#region Members

		private NotifyIcon m_tray;

		private JabberClient m_client;
		private RosterManager m_roster;
		private PresenceManager m_presence;
		private List<Item> m_contacts;
		private Dictionary<JID, Presence> m_online;
		private Dictionary<JID, ChatForm> m_chats;
		private bool m_rosterDone;
		private VCard m_myVCard;

#if WINDOWS
		[DllImport("user32.dll")]
		static extern bool FlashWindow(IntPtr hwnd, bool bInvert);
#endif

		#endregion

		#region Constructors

		/// <summary>
		/// Constructor
		/// </summary>
		public RosterForm()
		{
			this.InitializeComponent(); // Init UI

			// Init presence types
			this.cbStatus.Items.Add(Resources.Online);
			this.cbStatus.Items.Add(Resources.Away);
			this.cbStatus.Items.Add(Resources.Offline);

			// Create instance of collections and dictionaries
			this.m_contacts = new List<Item>();
			this.m_online = new Dictionary<JID, Presence>();
			this.m_chats = new Dictionary<JID, ChatForm>();

			// Initialize tray
			this.m_tray = new NotifyIcon();
			this.m_tray.Click += new EventHandler(Tray_Click);
			IntPtr hIcon = Resources.FacebookImage.GetHicon();
			this.m_tray.Icon = Icon.FromHandle(hIcon);
			this.m_tray.Text = this.Text;
			this.m_tray.Visible = true;

			// Initialize conact list
			this.lxFriends.DisplayMember = "Nickname";
			this.lxFriends.DrawItem += new DrawItemEventHandler(lxFriends_DrawItem);
			this.lxFriends.ItemHeight = 24;
			this.lxFriends.DoubleClick += new EventHandler(lxFriends_DoubleClick);
			this.lxFriends.KeyDown += new KeyEventHandler(lxFriends_KeyDown);

			// We need that for removing trayicon after exit main thread
			Application.ThreadExit += new EventHandler(Application_ThreadExit);
		}

		#endregion

		#region Methods

		/// <summary>
		/// Method returns instance of <see cref="Presence"/> if there's contact online or 
		/// <strong>null</strong> if there's not.
		/// </summary>
		/// <param name="jid"><see cref="JID"/></param>
		/// <returns><see cref="Presence"/></returns>
		private Presence IsOnline(JID jid)
		{
			foreach (JID key in this.m_online.Keys)
				if (jid.Bare == key.Bare)
					return this.m_online[key];
			
			return null;
		}

		/// <summary>
		/// Method returns roster item associated with <paramref name="jid"/>
		/// </summary>
		/// <param name="jid"><see cref="JID"/></param>
		/// <returns><see cref="Item"/></returns>
		private Item GetRosterItem(JID jid)
		{
			return this.m_contacts.Find(new Predicate<Item>(delegate(Item match)
			{
				return match.JID.Bare == jid.Bare;
			}));
		}

		/// <summary>
		/// Shows login form
		/// </summary>
		private void ShowLogin()
		{
			using (LoginForm login = new LoginForm())
			{
				if (login.ShowDialog() == DialogResult.OK)
				{
					this.m_client = login.Client;
					this.m_presence = new PresenceManager();
					this.m_roster = new RosterManager();

					this.m_client.OnPresence += new PresenceHandler(this.OnPresence);
					this.m_client.OnMessage += new MessageHandler(this.OnMessage);
					this.m_roster.OnRosterBegin += new bedrock.ObjectHandler(this.Roster_OnRosterBegin);
					this.m_roster.OnRosterEnd += new bedrock.ObjectHandler(this.Roster_OnRosterEnd);
					this.m_roster.OnRosterItem += new RosterItemHandler(this.Roster_OnRosterItem);

					this.m_presence.Stream =
						this.m_roster.Stream = this.m_client;
					this.cbStatus.SelectedIndex = 0;
					this.m_client.GetRoster();
					this.Show();
				}
				else
				{
					Application.Exit();
				}
			}
		}

		/// <summary>
		/// Opens chat window, if neccessary
		/// </summary>
		/// <param name="item">Roster item</param>
		private void OpenChat(Item item)
		{
			this.OpenChat(item, null);
		}

		/// <summary>
		/// Opens chat window, if necessary, with initial message
		/// </summary>
		/// <param name="item">Roster item</param>
		/// <param name="message">Initial message</param>
		private void OpenChat(Item item, string message)
		{
			// If there already exists chat window with this contact
			if (this.m_chats.ContainsKey(item.JID))
			{
				ChatForm form = this.m_chats[item.JID]; // Get instance
				if (form.WindowState == FormWindowState.Minimized || !form.IsActive) // Shall we pop?
				{
#if WINDOWS
					RosterForm.FlashWindow(form.Handle, false); // Flash window
#endif
					ChatForm.Pop(); // POP!!!1
				}
				return;
			}

			// Create instance of a new chat window
			ChatForm chatWindow = message == null? new ChatForm(item, this.m_client) : new ChatForm(item, this.m_client, message);
			chatWindow.Show(); // Show
			chatWindow.Closed += new EventHandler(this.ChatWindow_Closed); // Register closed event
			this.m_chats.Add(item.JID, chatWindow); // Add to dictionary
		}

		#endregion

		#region Override Methods

		protected override void OnLoad(EventArgs e)
		{
			this.ShowLogin();
			base.OnLoad(e);
		}

		#endregion

		#region Event Handlers

		void Application_ThreadExit(object sender, EventArgs e)
		{
			Application.ThreadExit -= new EventHandler(Application_ThreadExit);
			if (this.m_tray != null)
				this.m_tray.Visible = false;
		}

		void Tray_Click(object sender, EventArgs e)
		{
			if (this.m_client == null || !this.m_client.IsAuthenticated)
				return;

			if (this.Visible)
				this.Hide();
			else
				this.Show();
		}

		void lxFriends_KeyDown(object sender, KeyEventArgs e)
		{
			if (this.lxFriends.SelectedIndex == -1)
				return;
			if (e.KeyCode == Keys.Enter)
				this.OpenChat((Item)this.lxFriends.SelectedItem);
		}

		private void ChatWindow_Closed(object sender, EventArgs e)
		{
			ChatForm chatWindow = (ChatForm)sender;
			chatWindow.Closed -= new EventHandler(this.ChatWindow_Closed);
			JID keyFound = null;
			foreach (string key in this.m_chats.Keys)
				if (this.m_chats[key] == chatWindow)
				{
					keyFound = key;
					break;
				}

			if (!string.IsNullOrEmpty(keyFound))
				this.m_chats.Remove(keyFound);
		}

		void lxFriends_DoubleClick(object sender, EventArgs e)
		{
			if (lxFriends.SelectedIndex == -1)
				return;
			this.OpenChat((Item)this.lxFriends.SelectedItem);
		}

		void lxFriends_DrawItem(object sender, DrawItemEventArgs e)
		{
			if (!this.m_rosterDone || e.Index == -1 || this.lxFriends.Items.Count == 0)
				return;
			e.DrawBackground();

			Item item = (Item)this.lxFriends.Items[e.Index];
			SizeF size = e.Graphics.MeasureString(item.Nickname, e.Font);
			e.Graphics.DrawString(item.Nickname,
				e.Font, new SolidBrush(e.ForeColor),
				new PointF(
					(float)e.Bounds.X + 22, 
					(float)e.Bounds.Y + ((e.Bounds.Height - size.Height) / 2)));

			Presence presence = this.IsOnline(item.JID);
			if (presence != null)
			{
				e.Graphics.DrawImage(
					string.IsNullOrEmpty(presence.Show) ?
					Resources.OnlineImage : Resources.AwayImage,
					new Point(e.Bounds.X + 1,
						e.Bounds.Y + ((e.Bounds.Height - 20) / 2)));

				if (item.JID.Resource == "FDC")
				{
					int x = this.lxFriends.Width - 20;
					if (x > 0)
						e.Graphics.DrawImage(Resources.FacebookImage, x, 0);
				}
			}
			else
			{
				// Well I'm not proud of this shit
				this.lxFriends.Items.Remove(item);
			}
		}

		private void OnMessage(object sender, jabber.protocol.client.Message msg)
		{
			if (string.IsNullOrEmpty(msg.Body))
				return;
			Item item = this.GetRosterItem(msg.From);
			if (item == null)
			{
				return;
			}
			this.Invoke(new MethodInvoker(delegate { this.OpenChat(item, msg.Body); }));
		}

		private void OnPresence(object sender, Presence pres)
		{
			Item item = this.GetRosterItem(pres.From); // Get roster item from JID
			Presence presence = this.IsOnline(pres.From); // Get presence from JID

			// Friend went offline
			if(pres.Type == PresenceType.unavailable)
			{
				if (item != null)
				{
					if (presence != null) // Remove from online collection
						this.m_online.Remove(pres.From);
					// Refresh contact list
					this.Invoke(new MethodInvoker(delegate
					{
						this.lxFriends.Items.Remove(item);
						this.lxFriends.Refresh();
					}));
				}
				return;
			}

			// Friend went online (or away)
			if (pres.Type == PresenceType.available)
			{
				if (presence == null) // Is he in online collecton?
					this.m_online.Add(pres.From, pres); // Add him
				else
					this.m_online[pres.From] = pres; // Update his status

				item = this.GetRosterItem(pres.From); // Get roster item (again)
				if (item != null)
				{
					this.Invoke(new MethodInvoker(delegate
						{
							// Add him to contact list
							if (!this.lxFriends.Items.Contains(item) && presence == null)
								this.lxFriends.Items.Add(item);
							else
								this.lxFriends.Refresh(); // Refresh
						}));
				}
			}
		}

		void Roster_OnRosterItem(object sender, jabber.protocol.iq.Item ri)
		{
			this.m_contacts.Add(ri); // Add roster item to collection
		}

		void Roster_OnRosterEnd(object sender)
		{
			this.m_rosterDone = true;
		}

		void Roster_OnRosterBegin(object sender)
		{
			this.m_rosterDone = false;
			this.m_contacts.Clear();
		}

		private void cbStatus_SelectedIndexChanged(object sender, EventArgs e)
		{
			// React on my own status change
			switch (this.cbStatus.SelectedIndex)
			{
				case 0:
					this.m_client.Presence(PresenceType.available, "online", "online", 127);
					break;
				case 1:
					this.m_client.Presence(PresenceType.available, "away", "away", 127);
					break;
				case 2:
					if (MessageBox.Show(Properties.Resources.LogOffQuestion, Properties.Resources.LogOff, 
						MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
					{
						// Close all opened chat windows
						foreach (JID jid in this.m_chats.Keys)
							this.m_chats[jid].Close();
						// Clear collections
						this.m_chats.Clear();
						this.m_online.Clear();
						this.m_contacts.Clear();
						// Unregister events
						this.m_client.OnPresence -= new PresenceHandler(this.OnPresence);
						this.m_client.OnMessage -= new MessageHandler(this.OnMessage);
						this.m_roster.OnRosterBegin -= new bedrock.ObjectHandler(this.Roster_OnRosterBegin);
						this.m_roster.OnRosterEnd -= new bedrock.ObjectHandler(this.Roster_OnRosterEnd);
						this.m_roster.OnRosterItem -= new RosterItemHandler(this.Roster_OnRosterItem);
						// Close and dispose client
						this.m_client.Close();
						this.m_client.Dispose();
						this.Hide();
						// Show login
						this.ShowLogin();
						return;
					}

					this.cbStatus.SelectedIndex = 0;
					break;

			}
		}

		#endregion

	}
}
